/* textstream - a library for rendering scrolling text
 * Copyright (C) 2011 Jack Kelly <jack@jackkelly.name>
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "textstream.h"

#include "textstream_event.h"

static void textstream_nl(struct textstream* ts);
static void textstream_putc(struct textstream* ts, gint ch);

static void textstream_nl(struct textstream* ts) {
  if (ts->needs_newline || ts->f437->cursor_y + 1 == ts->f437->height) {
    /* Shift everything up a row */
    SDL_Rect src = {
      .x = ts->f437->left,
      .y = ts->f437->top + ts->f437->font->char_height,
      .w = ts->f437->width * ts->f437->font->char_width,
      .h = (ts->f437->height - 1) * ts->f437->font->char_height
    };
    SDL_Rect dst = {
      .x = ts->f437->left,
      .y = ts->f437->top
    };
    /* TODO: what if this fails? */
    SDL_BlitSurface(ts->f437->surface, &src, ts->f437->surface, &dst);
    SDL_Rect blank = {
      .x = ts->f437->left,
      .y = ts->f437->top + (ts->f437->height - 1) * ts->f437->font->char_height,
      .w = ts->f437->width * ts->f437->font->char_width,
      .h = ts->f437->font->char_height
    };
    SDL_FillRect(ts->f437->surface, &blank, ts->black);
    f437_surface_goto_xy(ts->f437, 0, ts->f437->height - 1);
    ts->needs_newline = FALSE;
  } else {
    f437_surface_goto_xy(ts->f437, 0, ts->f437->cursor_y + 1);
  }
}

static void textstream_putc(struct textstream* ts, gint ch) {
  if (ts->needs_newline) textstream_nl(ts);
  if (ts->f437->cursor_x == ts->f437->width - 1
      && ts->f437->cursor_y == ts->f437->height - 1) {
    ts->needs_newline = TRUE;
  }
  f437_surface_putc(ts->f437, ch);
}

struct textstream* textstream_new(SDL_Surface* surface,
                                  const F437Font* font,
                                  gint scale) {
  g_return_val_if_fail(surface != NULL, NULL);
  struct textstream* ts = g_new(struct textstream, 1);
  *ts = (struct textstream) {
    .f437 = f437_surface_new(surface, font, scale),
    .speed = 1,
    .needs_newline = FALSE,
    .black = SDL_MapRGB(surface->format, 0, 0, 0),
    .events = G_QUEUE_INIT
  };
  return ts;
}

SDL_Surface* textstream_free(struct textstream* ts) {
  g_return_val_if_fail(ts != NULL, NULL);
  SDL_Surface* surface = f437_surface_free(ts->f437);
  void free_event(gpointer event, gpointer userdata) {
    (void)userdata;
    textstream_event_free(event);
  }
  g_queue_foreach(&ts->events, free_event, NULL);
  g_queue_clear(&ts->events);
  g_free(ts);
  return surface;
}

void textstream_add(struct textstream* ts, const gchar* str) {
  g_return_if_fail(ts != NULL);
  g_return_if_fail(str != NULL);
  g_queue_push_tail(&ts->events,
                    textstream_text_event_new(str, FALSE));
}

void textstream_add_instant(struct textstream* ts, const gchar* str) {
  g_return_if_fail(ts != NULL);
  g_return_if_fail(str != NULL);
  g_queue_push_tail(&ts->events,
                    textstream_text_event_new(str, TRUE));
}

void textstream_fg(struct textstream* ts, F437Color fg) {
  g_return_if_fail(ts != NULL);
  g_queue_push_tail(&ts->events,
                    textstream_fg_event_new(fg));
}

void textstream_bg(struct textstream* ts, F437Color bg) {
  g_return_if_fail(ts != NULL);
  g_queue_push_tail(&ts->events,
                    textstream_bg_event_new(bg));
}

void textstream_newline(struct textstream* ts) {
  g_return_if_fail(ts != NULL);
  g_queue_push_tail(&ts->events,
                    textstream_newline_event_new());
}

void textstream_clear(struct textstream* ts) {
  g_return_if_fail(ts != NULL);
  SDL_Rect blank = {
    .x = ts->f437->left,
    .y = ts->f437->top,
    .w = ts->f437->width * ts->f437->font->char_width,
    .h = ts->f437->height * ts->f437->font->char_height
  };
  SDL_FillRect(ts->f437->surface, &blank, ts->black);
  f437_surface_goto_xy(ts->f437, 0, 0);
  ts->needs_newline = FALSE;
}

gboolean textstream_update(struct textstream* ts) {
  g_return_val_if_fail(ts != NULL, FALSE);
  struct textstream_event *ev = g_queue_peek_head(&ts->events);
  if (ev == NULL) return FALSE;

  gboolean free = TRUE; /* Free the event */
  gboolean repeat = TRUE; /* Didn't print anything, so update again */
  gboolean printed = FALSE; /* Something was printed */

  switch (ev->type) {
  case TEXTSTREAM_EVENT_TEXT:
    if (ev->text.instant) {
      const gchar* ch = ev->text.str;
      while (*ch != '\0') textstream_putc(ts, *ch++);
    } else {
      if (ev->text.str[ev->text.i] == '\0') break;
      textstream_putc(ts, ev->text.str[ev->text.i++]);
      repeat = FALSE;
      free = FALSE;
    }
    printed = TRUE;
    break;
  case TEXTSTREAM_EVENT_FG:
    f437_surface_fg(ts->f437, f437_get_color(ev->fg));
    break;
  case TEXTSTREAM_EVENT_BG:
    f437_surface_bg(ts->f437, f437_get_color(ev->bg));
    break;
  case TEXTSTREAM_EVENT_NEWLINE:
    textstream_nl(ts);
    repeat = FALSE;
    printed = TRUE;
    break;
  }

  if (free) {
    textstream_event_free(ev);
    g_queue_pop_head(&ts->events);
  }
  if (repeat) printed |= textstream_update(ts);
  return printed;
}
